require 'hmac-sha1'
require 'digest/sha1'
require 'base64'
require 'cgi'

module API
  module Helpers
    module OAuth
      def is_authorized?
        begin
          auth_token = get_auth_token
          # if auth_token is avaliable
          if auth_token
            message = construct_url + params[:client_id].to_s + params[:timestamp].to_s
            salt = Digest::SHA1.hexdigest(message)[0..19]
            signature = Base64.urlsafe_encode64(HMAC::SHA1.digest(auth_token, salt)).strip            
            DebugLog.add_to_log auth_token: auth_token, message: message, salt: salt, signature: signature
            unless signature == params[:signature]
              @error_code = 4
            end
          end

        rescue Exception => e
          DebugLog.add_to_log exception: e
          raise
        ensure
          # log
          DebugLog.add_to_log result: get_error_msg(@error_code) || true

          # is there's an error, exit with error
          if @error_code
            error = { message: get_error_msg(@error_code), error_code: @error_code }
            error[:timestamp] = Time.now.to_i if @error_code == 2
            render_api_error!(error, 401, plain = true)
          end
        end
      end

      private      
        def get_auth_token
          DebugLog.add_to_log client_id: params[:client_id], timestamp: params[:timestamp], origin_signature: params[:signature]

          if params[:client_id].blank? || params[:timestamp].blank? || params[:signature].blank?
            return (@error_code = 1) && nil
          end

          if (params[:timestamp].to_i - Time.now.to_i).abs > 180
            return (@error_code = 2) && nil
          end

          auth_token = AuthToken.find_by_id(params[:client_id])
          # currently, we'll not check whether the auth_token is expired
          if !auth_token || auth_token.expired_timestamp < params[:timestamp].to_i
            return (@error_code = 3) && nil
          end

          auth_token.token
        end

        def construct_url
          request = Rack::Request.new(env)
          request_path = (request.port == 443 ? 'https' : 'http') + '://' + request.host_with_port + request.path
          # it will not delete the origin params, but the copy, so it's safe
          request_get = clear_signature_params request.GET
          request_post = clear_signature_params request.POST

          if request_get.empty? && request_post.empty?
            url = request_path
          else
            url = request_path + '?' + (convert_params_hash(request_get) + convert_params_hash(request_post)).sort.join('&')
          end

          DebugLog.add_to_log request_path: request_path, request_get: request_get, request_post: request_post, url: url
          url
        end

        def clear_signature_params origin_params
          need_filter_params = ['client_id', 'timestamp', 'signature']
          # remove attachment params
          origin_params.delete_if { |key, value| need_filter_params.include?(key) || value.respond_to?(:has_key?) && value.has_key?(:tempfile) }
        end

        def convert_params_hash params_hash
          params_hash.to_param.split('&')
        end

        def get_error_msg code
          {
            1 => 'OAuth fail: without complete signature information',
            2 => 'OAuth fail: timestamp is out-of-date',
            3 => 'OAuth fail: unavaliable auth_token',
            4 => 'OAuth fail: signature is incorrect'
          }[code]
        end
    end
  end
end